package com.zoffy.zoffyapigateway.filter;



import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSON;
import com.zoffy.zoffyapicommon.model.entity.InterfaceInfo;
import com.zoffy.zoffyapicommon.model.entity.User;
import com.zoffy.zoffyapicommon.model.entity.UserInterfaceInfo;
import com.zoffy.zoffyapicommon.service.InnerInterfaceInfoService;
import com.zoffy.zoffyapicommon.service.InnerUserInterfaceInfoService;
import com.zoffy.zoffyapicommon.service.InnerUserService;
import com.zoffy.zoffyapigateway.common.BusinessException;
import com.zoffy.zoffyapigateway.common.ErrorCode;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {
    @DubboReference
    private InnerInterfaceInfoService innerInterfaceInfoService;
    @DubboReference
    private InnerUserService innerUserService;
    @DubboReference
    private InnerUserInterfaceInfoService innerUserInterfaceInfoService;
    @Resource
    RedisTemplate<String,Object> redisTemplate;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        HttpHeaders headers = request.getHeaders();
        String headerParamsString=headers.getFirst("signature");
        HashMap headerParams;
        try{
            headerParams = JSON.parseObject(Base64.decode(headerParamsString), HashMap.class);
        }catch (Exception e){
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR,"鉴权失败");
        }
        String accessKey =(String) headerParams.getOrDefault("accessKey","");
        String timestampString =(String) headerParams.getOrDefault("timestamp","");
        String nanos =(String) headerParams.getOrDefault("nanos","");
        String signature =(String) headerParams.getOrDefault("signature","");
        String interfaceIdString =(String) headerParams.getOrDefault("interfaceId","");
        long interfaceId = Long.parseLong(interfaceIdString);

        //非空校验
        if(StringUtils.isAnyBlank(accessKey,timestampString,nanos,signature,interfaceIdString)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //检验时间戳

        assert timestampString != null;
        long timestamp = Long.parseLong(timestampString);
        long currentTimeMillis = System.currentTimeMillis()/1000;
        if (currentTimeMillis-timestamp>20){
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR,"请求超时拒绝");
        }

        //校验接口是否开放
        InterfaceInfo interfaceInfo = innerInterfaceInfoService.getInterface(interfaceId);
        Integer status = interfaceInfo.getStatus();
        if (status!=1){
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR,"接口已关闭");
        }

        // 使用redis验证存储随机数
        String key="nanos:"+nanos;
        String value =(String) redisTemplate.opsForValue().get(key);
        if(value!=null){
            throw new BusinessException(ErrorCode.FORBIDDEN_ERROR,"疑似攻击");
        }
        redisTemplate.opsForValue().set(key,"1",2, TimeUnit.SECONDS);


        //校验是否开通接口与是否有接口剩余调用次数
        User user=innerUserService.getInvokeUser(accessKey);
        assert interfaceIdString != null;
        UserInterfaceInfo userInterfaceInfo = innerUserInterfaceInfoService.getUserInterfaceInfo(user.getId(), interfaceId);
        if (userInterfaceInfo==null || userInterfaceInfo.getLeftNum()<=0){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR,"接口未开通或无调用次数");
        }

        String secretKey = user.getSecretKey();
        Long userId = user.getId();
        //校验签名
        String trueSignature = DigestUtil.sha256Hex(accessKey + timestamp + nanos + secretKey);
        if(!trueSignature.equals(signature)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"验证未通过");
        }
        // 接口统计扣除
        boolean flag = innerUserInterfaceInfoService.handleInvoke(userId, interfaceId);
        if(!flag){
            throw new BusinessException(ErrorCode.SYSTEM_ERROR,"接口计费异常");
        }

        //鉴权成功，开始转发

        String url = interfaceInfo.getUrl();
        return transmit(exchange,chain,url);


    }
    public Mono<Void> transmit(ServerWebExchange exchange, GatewayFilterChain chain, String newPath){


        // 动态修改路由开始
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        URI uri = UriComponentsBuilder.fromHttpUrl(newPath).queryParams(queryParams).build().toUri();
        log.info(request.getQueryParams().toString());
        ServerHttpRequest newRequest = request.mutate().uri(uri).build();
        Route route =exchange.getAttribute(GATEWAY_ROUTE_ATTR);
        if (route ==null){
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        Route newRoute = Route.async()
                .asyncPredicate(route.getPredicate())
                .filters(route.getFilters())
                .id(route.getId())
                .order(route.getOrder())
                .uri(uri)
                .build();
        exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, newRoute);
        log.info(newRequest.getQueryParams().toString());
        return chain.filter(exchange.mutate().request(newRequest).build());
    }


    @Override
    public int getOrder() {
        return -1;
    }
}
